
#include "vty_session.hpp"
#include "vty_manager.hpp"

#include <termios.h>
#include <unistd.h>
#include <sys/wait.h>
#include <arpa/telnet.h>    /* for telnet opt */
#include <stdarg.h> /* for va_start, va_end */ 
#include <pty.h>

#include <pthread.h>
#include <signal.h>

#include "command_manager.hpp"

bool compare_nocase (cmd_ptr fcmd, cmd_ptr scmd);
void print_cmd_desc(cmd_list_iterator begin, cmd_list_iterator end, bool is_exec);

void change_newline(std::string& buf)
{
	char target = '\r';
	std::string goal("\n");
	std::size_t f = 0, pos = 0;

	while (1) {
		f = buf.find(target, pos);
		if (f == std::string::npos)
			break;
		buf.replace(f, 1,goal);
		pos = (f+1);
	}
};



vty_session::vty_session(__u32 id, io_service& io_service, vty_manager& manager,
        command_manager& cmdmng):
    _id(id),
    _flag(false),
    _io_service(io_service),
    _strand(_io_service),
    _socket(new tcp_socket(_io_service)), 
    _manager(manager),
    _cmdmng(cmdmng),
    _pty_shell(new pty_shell()),
    _mode("ENABLE"), _diag(NULL),
    _is_stopping(false),
    _osbuf(_def_max_buf_size_),
    _pty_osbuf(_def_max_buf_size_),
    _ostream(&_osbuf),
    _pty_ostream(&_pty_osbuf)
{
    if (is_pty()) PDBG("is_pty is true\n");
    else PDBG("is_pty is false\n");
    PDBG("create vty session\n");
}

void vty_session::receive_alram(const std::string& msg)
{
	if(is_pty()) return ;	/* FIXME */
	bool in_progress = !_alarm_q.empty();
	_alarm_q.push_back(msg);
	if (!in_progress) {
		boost::asio::async_write(
				this->socket(),
				boost::asio::buffer(_alarm_q.front().data(),
					_alarm_q.front().length()),
				boost::bind(&vty_session::handle_write_alarm,
					shared_from_this(),
					boost::asio::placeholders::error,
					boost::asio::placeholders::bytes_transferred));
	}
}

void vty_session::excute_command(const std::string commandline) 
{
    if (_diag != NULL) {
        _diag(shared_from_this(), commandline);
    } else if (_cmdmng.excute_command(shared_from_this(), commandline) != true) {
        this->output("%% unkwon command: %s%s", commandline.c_str(), VTY_NEWLINE);
    }
}

void vty_session::describe_command(const std::string cmdline) 
{
    bool is_child = false, is_exec = false, ret=false;
    size_t size = cmdline.size();
    cmd_list_t  cmdlist;

    this->output("%s", VTY_NEWLINE);  
    if (size != 0 && cmdline[size-1] == ' ') is_child = true;
    ret = _cmdmng.describe_command(shared_from_this(), cmdline, cmdlist, is_child, is_exec);
    if (ret == false) {
        this->output("There is no matched command.%s", VTY_NEWLINE);
    } else { 
        cmdlist.sort(compare_nocase);   /* FIXME: move to where this list is created!! */
        cmd_list_iterator iter = cmdlist.begin();
        for (; iter != cmdlist.end(); iter++) {
            this->output("%-10s\t%s%s", (*iter)->get_cmd().c_str(), 
                    (*iter)->get_help().c_str(), VTY_NEWLINE);
        }
        if (is_exec) this->output("<CR>%s", VTY_NEWLINE);  
    }

    _input->prompt();
    _input->redraw_line();
    return ;
}

void vty_session::complete_command(const std::string cmdline) 
{
    bool is_child = false, is_exec = false, ret=false;
    size_t size = cmdline.size(), cnum = 0;
    cmd_list_t  cmdlist;
    std::string newcmdline;
    cmd_ptr ptr;

    if (size != 0 && cmdline[size-1] == ' ') is_child = true;
    ret = _cmdmng.describe_command(shared_from_this(), cmdline, cmdlist, is_child, is_exec);
    if (ret != true) 
        return;
    cnum = cmdlist.size();
    if (cnum == 1) {
        this->output("%s", VTY_NEWLINE);
        ptr = (*cmdlist.begin());
        newcmdline = cmdline;
        if (ptr->get_type() == CMD_TYPE) {
            std::size_t pos = newcmdline.rfind(" ");
            if (pos != std::string::npos) {
                std::string word = ptr->get_cmd();
                newcmdline.replace(pos + 1, word.length(), word);
            } else {
                newcmdline = ptr->get_cmd();
            }
            newcmdline.append(" ");
        }
        _input->prompt();
        _input->redraw_line(newcmdline);
        return ;
    } else if (cnum != 0) {
        this->output("%s", VTY_NEWLINE);
        cmdlist.sort(compare_nocase);   /* FIXME: move to where this list is created!! */
        cmd_list_iterator iter = cmdlist.begin();
        for (; iter != cmdlist.end(); iter++) {
            this->output("%s\t", (*iter)->get_cmd().c_str());
        }
    }
    
    this->output("%s", VTY_NEWLINE);
    _input->prompt();
    _input->redraw_line();
}

void vty_session::excute_pty(const string_vector& cmdvec)
{
    if(_ptystream.use_count() != 0) return;

    this->output(VTY_NEWLINE);

    int ptyfd = _pty_shell->open_pty(cmdvec);
    if (!(ptyfd > 0)) return;

    /* FIXME */
    _ptystream.reset(new posix::stream_descriptor(_io_service, ::dup(ptyfd)));
    flush(_pty_input_);
}

void vty_session::close_pty() 
{
    PDBG("start close_pty\n");
    if (_ptystream.use_count() == 0) return;
    _ptystream->close();
    _ptystream.reset();
    PDBG("use count: %lu\n", _ptystream.use_count());
};

void vty_session::stop()
{
    _is_stopping = true;
} 

void vty_session::close()
{
    PDBG(">>>>>>>>>>>>>>> start close <<<<<<<<<<<<<<\n");
    _socket->close();
    this->close_pty();
    //_input.reset();
    _manager.leave(shared_from_this());
    PDBG(">>>>>>>>>>>>>>> end close <<<<<<<<<<<<<<\n");
};

void vty_session::start()
{
    _manager.join(shared_from_this());

    _input.reset(new vty_input(shared_from_this()));
    _input->start();
    {   /* FIXME */
		static const char iacs_to_send[] = {
			IAC, DO, TELOPT_ECHO,
			IAC, DO, TELOPT_NAWS,
			IAC, DO, TELOPT_LFLOW,
			IAC, WILL, TELOPT_ECHO,
			IAC, WILL, TELOPT_SGA
		};
        this->output(iacs_to_send, sizeof(iacs_to_send));
	}

    flush(_socket_input_);
    flush(_socket_output_); /* FIXME */
};

/* FIXME */
void vty_session::handle_ingress_sock(std::string& input)
{
    write_pty(input);
}

int vty_session::output(const char c)
{
    _ostream << c;
    return 0;
};

int vty_session::output(const std::string& string) 
{
    _ostream << string;
    return 0;
};

int vty_session::output(const char* fmt, ...) 
{
    va_list     args;
    std::size_t len;
    char buf[8192];  /* FIXME: change memory pool */

    va_start (args, fmt);
    len = vsnprintf (buf, sizeof buf, fmt, args);
    va_end (args);

    if (len > 8192) {
        throw std::runtime_error("overflow internal buffer!!!!");
        return -1; /* FIXME: kauri */
    }
    buf[len] = '\0';
    _ostream << buf; 
    return 0;
};

void vty_session::write_sock() 
{

    boost::asio::async_write(
            this->socket(),
            _osbuf,
            _strand.wrap(
				boost::bind(&vty_session::handle_write_sock,
					shared_from_this(),
					boost::asio::placeholders::error,
					boost::asio::placeholders::bytes_transferred)));
}

void vty_session::write_pty(const std::string data)
{
    _pty_ostream << data;
    boost::asio::async_write(
            *_ptystream,
			_pty_osbuf,
			_strand.wrap(
				boost::bind(&vty_session::handle_write_pty,
					shared_from_this(),
					boost::asio::placeholders::error,
					boost::asio::placeholders::bytes_transferred)));
}

void vty_session::flush(direction dir)
{
    if (is_pty()) { /* FIXME */
        if (_pty_shell->get_pid() == 0) {
            close_pty();
        }
    }

    try {
    if (dir == _all_ || dir == _socket_input_) {
        _socket->async_read_some(
                boost::asio::buffer(_sbuf),
                _strand.wrap(boost::bind(&vty_session::handle_read_sock,
                    shared_from_this(),
                    boost::asio::placeholders::error,
                    boost::asio::placeholders::bytes_transferred)));
    }
    if (dir == _all_ || dir == _socket_output_) {
        write_sock();
    }
    if (dir == _all_ || dir == _pty_input_) {
        if (is_pty())
            _ptystream->async_read_some(
                    boost::asio::buffer(_pbuf),
                    _strand.wrap(boost::bind(&vty_session::handle_read_pty,
                        shared_from_this(),
                        boost::asio::placeholders::error,
                        boost::asio::placeholders::bytes_transferred)));
    }
    }catch (std::exception& e) {
        std::cerr << "Exception: " << e.what() << "\n";
    }
    return;
}


void vty_session::handle_read_sock( const boost::system::error_code& e,
        std::size_t bytes_transferred) 
{
    if (!e) {
        //PDBG("bytes_transferred : %d\n", bytes_transferred);
        std::string tmp(_sbuf.begin(), _sbuf.begin() + bytes_transferred);
        if (!this->is_pty()) {
            _input->handle_input(tmp);
        } else {
			//change_newline(tmp);
            PDBG("write_pty: ");
			for (int idx=0; idx < tmp.size(); idx++) {
				printf("%d ", tmp[idx]);
			}
			printf("\n");
            this->write_pty(tmp);
        };
        if (is_stop()) close();
    } else {
        PDBG("exception: %s\n", e.message().c_str());
        close();
        return ;
    }
    flush(_socket_input_);
    flush(_socket_output_);
}

void vty_session::handle_write_sock( const boost::system::error_code& e,
        std::size_t bytes_transferred)
{
    if (!e) {
        //PDBG("bytes_transferred : %d\n", bytes_transferred);
        _osbuf.consume(bytes_transferred);
    } else {
        PDBG("exception: %s\n", e.message().c_str());
    }
}

void vty_session::handle_write_alarm( const boost::system::error_code& e,
        std::size_t bytes_transferred)
{
    if (!e) {
        PDBG("bytes_transferred : %d\n", bytes_transferred);
		_alarm_q.pop_front();
		if (!_alarm_q.empty()) {	/* FIXME */
			boost::asio::async_write(
					this->socket(),
					boost::asio::buffer(_alarm_q.front().data(),
						_alarm_q.front().length()),
					_strand.wrap(boost::bind(&vty_session::handle_write_alarm,
						shared_from_this(),
						boost::asio::placeholders::error,
						boost::asio::placeholders::bytes_transferred)));
		}
    } else {
        PDBG("exception: %s\n", e.message().c_str());
    }
}

void vty_session::handle_write_pty( const boost::system::error_code& e,
        std::size_t bytes_transferred) 
{
    if (!e) {
        //PDBG("bytes_transferred : %d\n", bytes_transferred);
        _pty_osbuf.consume(bytes_transferred);
    } else {
        PDBG("exception: %s\n", e.message().c_str());
        this->close_pty();
        return ;
    }
}

void vty_session::handle_read_pty( const boost::system::error_code& e,
        std::size_t bytes_transferred)
{
    if (!e) {
        if (is_pty()) { PDBG(" pty mode !!!\n"); };
        if (_ptystream->is_open())  { PDBG(" pty is opend!!!!\n"); };
        std::string tmp(_pbuf.begin(), _pbuf.begin() + bytes_transferred);
        this->output(tmp);
    } else {
        PDBG("exception: %s\n", e.message().c_str());
        this->close_pty();
        return ;
    }
    flush(_pty_input_);
    flush(_socket_output_);
}

bool compare_nocase (cmd_ptr fcmd, cmd_ptr scmd)
{
    std::string first = fcmd->get_cmd();
    std::string second = scmd->get_cmd();
    return (first < second);
}

void print_cmd_desc(cmd_list_iterator begin, cmd_list_iterator end, bool is_exec) 
{
    cmd_list_iterator iter = begin;
    for (; iter != end; iter++) {
        printf("%-10s\t%s\n", (*iter)->get_cmd().c_str(), (*iter)->get_help().c_str());
    }
    if (is_exec)
        printf("<CR>\n");
}
